feat: make sessions worktree-aware - #642
Conversation
Keep OpenCode project, workspace, and directory identity in CodeNomad session state across list hydration, creation, forks, and session update events. Resolve worktree labels from the native session directory when OpenCode reports a workspace, while retaining the legacy metadata mapping as a migration fallback. Normalize Windows and POSIX path matching and cover native directory resolution with focused tests. Validated with the UI typecheck, focused OpenCode workspace matching tests, and git diff checks.
Create new sessions in the active OpenCode workspace and warp existing session families when users select another Git worktree. Route prompts, commands, compaction, reverts, files, Git status, interruption replies, and destructive session operations through the resolved native workspace so unresolved locations fail closed instead of falling back to the project root. Keep the historical CodeNomad worktree slug only as a lazy migration hint. Successful warps clear that metadata, update runtime location state, and remove legacy map entries; partial family moves roll back before any local state changes. Worktree deletion now refuses to proceed unless affected sessions move safely to root. Allow metadata markers to be removed in the server persistence layer while preserving unrelated metadata and resolving the session's current workspace before writes. Add focused tests for metadata removal, family migration, and rollback, and validate UI/server typechecks plus related store tests.
Add an accessible session-list menu for recent-activity, name, and native-worktree sorting plus per-worktree filtering. Keep families intact, apply projection after search, and align rendering, bulk selection, and deletion fallback with the visible result. Expose an Open in file manager action only in local Electron and Tauri desktop windows. Both native bridges validate the sender, reject network paths, canonicalize the requested directory, and verify it against Git's authoritative worktree inventory before invoking the system file manager. Harden worktree moves and deletion around the new UI: serialize family warps, refresh authoritative OpenCode locations, reject incomplete families, normalize Windows paths, block new worktree operations during deletion, and roll back physical locations and legacy metadata when preparation or Git deletion fails. Serialize legacy map updates and keep post-delete map cleanup best-effort. Add translated labels for every locale, focused migration/sorting/rollback tests to PR CI, and validate production UI build, all TypeScript typechecks, 118 Electron native tests, 40 focused UI tests, 5 server metadata tests, Tauri cargo check, and Rust formatting.
Allow legacy permission and question responses to use the root client when their session has not been hydrated yet, matching the existing SSE lifecycle. Keep strict native workspace resolution for loaded sessions so worktree-scoped requests still route through their authoritative OpenCode location. Reproduced the PR restore-ownership integration command locally and verified all 34 tests plus the UI typecheck.
A concurrently starting modern Electron client can publish its legacy running marker just before its cross-host participant. On slower Windows runners, Tauri could observe that transient state and both clients could remain secondary. Recheck a blocking legacy marker once after a bounded 50 ms grace period while preserving PID, process identity, and executable validation. Add a deterministic participant-publication race test. Validated with rustfmt, five cross-host startup runs, and the complete 85-test Tauri suite on Windows.
Run the Windows Tauri suite with one Rust test thread because several cross-host tests intentionally launch real Node and PowerShell processes. Parallel crate execution causes unrelated child-process starvation on GitHub's Windows runner while each test already exercises its own intended concurrency. Validated the exact CI command locally with all 85 Tauri tests passing.
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/31318727230 Artifacts expire in 7 days.
|
1 similar comment
|
PR builds are available as GitHub Actions artifacts: https://github.com/NeuralNomadsAI/CodeNomad/actions/runs/31318727230 Artifacts expire in 7 days.
|
pascalandr
left a comment
There was a problem hiding this comment.
Gatekeeper autonome - cycle 1
HEAD: 7285cf5
Findings:
- P1 - Les interruptions legacy des sessions occupées déclenchent une migration et peuvent bloquer permission, question et abort. Le contrôle doit utiliser la localisation native courante sans migration.
- P1 - Les réponses Yolo serveur omettent le workspace après un warp natif et ciblent root.
- P1 - La suppression ignore une famille dont seul un descendant reste dans le worktree cible.
- P1 - Le verrou de suppression est local au renderer; une autre fenêtre peut créer ou déplacer une session pendant la suppression.
- P1 - Une réponse DELETE perdue déclenche un rollback vers un worktree déjà supprimé.
- P1 - Une réponse de warp ambiguë peut scinder une famille tout en laissant le cache local obsolète.
- P2 - Les requêtes workspace-scoped ne conservent pas le lease familial jusqu'à la fin de l'opération.
- P2 - Une suppression Git échouée peut effacer définitivement un binding legacy virtuel.
- P2 - Le cache workspace Yolo reste obsolète lorsqu'une session revient vers root.
- P2 - Les opérations root échouent si l'hydratation des worktrees est indisponible.
- P2 - Navigation et fallback après bulk-delete ignorent la projection triée/filtrée affichée.
- P2 - repoRoot fourni par le renderer est utilisé comme autorité native pour ouvrir un dossier.
- P2 - La commande Tauri est accessible à un contenu localhost arbitraire sans validation de l'origine active.
- P3 - Electron valide le chemin canonique mais ouvre le chemin mutable original.
- P3 - Les worktrees UNC valides sont reconnus par l'UI mais systématiquement rejetés par l'action d'ouverture.
La review sera répétée sur chaque nouveau HEAD jusqu'à zéro finding.
Route workspace-scoped operations through per-family reader/writer leases so requests may proceed concurrently while moves remain exclusive. Reconcile ambiguous warps from authoritative OpenCode project state, preserve busy legacy control requests, support root without Git hydration, and carry authoritative workspace locations into server-side Yolo replies. Move worktree deletion coordination into the shared server. Drain admitted mutations, reject new writes during deletion, select families from every authoritative member, reconcile uncertain warps, roll back before the Git commit point, and clean legacy bindings only after successful removal. Renderer timeout recovery now checks authoritative Git inventory instead of warping toward a potentially deleted directory. Resolve desktop folder-open requests from authenticated backend workspace inventory rather than renderer paths. Electron opens only the canonical managed directory; Tauri additionally validates the claimed renderer token and committed origin. Keep UNC opening disabled and hide the unsupported action. Use the live rendered session projection for navigation and deletion fallback. Add focused regression coverage across UI routing, deletion, Yolo, Electron, and Tauri; validated 55 UI tests, 44 server tests, 122 Electron tests, 88 Tauri tests, all typechecks, and the production UI build.
pascalandr
left a comment
There was a problem hiding this comment.
Gatekeeper autonome - cycle 2
HEAD: 7b35196
Findings:
- P1 - Le lease des mutations proxy est libéré dès le retour synchrone de reply.from, avant la fin de la requête upstream.
- P1 - La transaction de suppression ne vérifie pas les statuts autoritaires working/compacting sous exclusivité.
- P1 - Les prompts directs du gestionnaire de processus de fond contournent le gate partagé.
- P1 - Le move familial reste coordonné uniquement dans le renderer; deux clients peuvent intercaler leurs warps.
- P2 - Une génération acceptée mais pas encore reflétée par SSE peut être déplacée.
- P1 - Un workspaceId natif non résolu retombe à tort sur metadata/map legacy.
- P1 - La migration legacy peut réécrire un slug obsolète pendant un move.
- P2 - L'écriture du binding session-worktree contourne la coordination de suppression et n'exige pas que le slug existe.
- P2 - La création de worktree peut courir avec la fin de suppression du même slug.
- P2 - Un PUT de map complet et obsolète peut annuler le nettoyage de suppression.
- P1 - Le client reqwest Tauri peut envoyer le cookie loopback via un proxy configuré; il doit utiliser no_proxy.
- P3 - La normalisation d'une racine Windows C:\ en C: perd la comparaison insensible à la casse.
Un troisième cycle indépendant sera lancé après correction et publication.
Execute complete family moves through one server-authoritative operation under the shared per-instance gate. Resolve complete ancestry and native targets, reject active or pending families, reconcile every member before returning, and clear legacy metadata/map bindings only after the native move commits. Hold proxied and direct server mutation leases through actual request completion. Serialize worktree creation, binding writes, map updates, moves, and deletion; validate slugs while admitted and use map revisions to reject stale full-document writes. Keep native workspace IDs authoritative in the UI, remove eager legacy migration, and apply only reconciled server move responses to local caches. Preserve Windows drive roots during path normalization and prevent Tauri loopback cookie requests from using configured proxies. Validated 52 focused UI tests, 59 focused server tests, 122 Electron tests, 88 Tauri tests, all typechecks, cargo check, and the production UI build.
pascalandr
left a comment
There was a problem hiding this comment.
Gatekeeper autonome - cycle 3
HEAD: 4177fa1
Findings:
- P1 - Une déconnexion client libère encore le gate avant que la mutation upstream soit confirmée terminée ou annulée.
- P1 - Un échec de nettoyage metadata/map après les warps laisse les nouvelles localisations commitées sans rollback transactionnel.
- P2 - Le prompt de fin de processus attend le gate mais réutilise ensuite un directory capturé avant un move/suppression.
- P2 - Une ancienne réponse HTTP de move peut écraser un événement session.updated plus récent provenant d'un autre client.
- P2 - Un move réussi vers root ne nettoie pas immédiatement le worktreeSlug legacy dans l'état local et peut afficher/réutiliser feature.
Le cycle 4 relira le nouveau HEAD complet après correction.
Keep proxy mutation admission until the upstream response or error is confirmed, even if the downstream client disconnects. Resolve background completion prompts from authoritative session location only after acquiring mutation admission. Treat family movement and legacy cleanup as one transaction. Restore metadata, revisioned map state, and native member locations when post-warp cleanup fails, and aggregate rollback failures instead of returning partial success. Track authoritative session-location epochs in the UI so older move responses cannot overwrite newer cross-client session events. Clear local legacy worktree metadata immediately after a reconciled move, including moves back to root. Validated 53 focused UI tests, 63 focused server tests, and all UI, Electron, and server typechecks.
pascalandr
left a comment
There was a problem hiding this comment.
Gatekeeper autonome - cycle 4
HEAD: 8eccaf8
Findings:
- P1 - Les listeners HTTP/HTTPS ont des gates distincts et les réponses Yolo directes les contournent.
- P1 - Une requête proxy interrompue avant headers peut ne déclencher aucun finalizer upstream et fuiter le lease.
- P1 - Une suppression peut orpheliner les sessions d'une autre instance live du même dépôt.
- P1 - Le CAS de map partagé par dépôt est verrouillé seulement par workspace ID.
- P1 - Une map malformée est convertie en fallback puis écrasée pendant la suppression.
- P1 - La suppression ne nettoie pas les autres bindings legacy des familles déplacées.
- P1 - Search et hydratation de parent-chain peuvent restaurer une ancienne localisation après session.updated.
- P2 - La garde de suppression se lève avant réconciliation autoritaire des sessions/workspaces/map.
- P2 - Une interruption legacy reçue avant hydratation de session est encore envoyée vers root.
- P2 - Fermer la sidebar démonte la projection alors que tri/filtre persistent, et la navigation redevient non filtrée.
- P2 - Les chemins Windows étendus équivalents ne sont pas normalisés pour l'identité workspace.
Le cycle 5 sera lancé après publication des corrections.
Route HTTP, HTTPS, background prompt, and Yolo mutations through the same process-wide repository gate so concurrent session moves and destructive operations cannot bypass serialization. Keep worktree deletion and map updates authoritative across live instances, reject shared-repository deletion, guard asynchronous UI reconciliation with epochs, and preserve navigation projections while the sidebar is closed. Add focused server and UI regression coverage for proxy lifecycle, strict map reads, canonical repository locking, early request interruption, deletion reconciliation, and stale workspace hydration.
Gatekeeper cycle 5\n\nReview target: \c76c1480\n\n1. High Repository mutation identity uses --show-toplevel, so linked worktrees receive different locks and live sibling detection can miss the instance being deleted (\workspace-identity.ts:21-23, \outes/worktrees.ts:161-169).\n2. High Workspace creation, explicit teardown, and shutdown do not consistently acquire the shared repository/instance mutation gate, allowing runtime startup or stop to race family moves and worktree deletion ( |
Identify linked worktrees by their canonical Git common directory and hold shared repository admission across workspace startup, shutdown, proxy mutations, Yolo persistence, family moves, and destructive cleanup. Re-resolve queued mutations after exclusive moves, preserve rollback and map revision semantics, retain UI deletion and location fences through authoritative reconciliation, and keep sidebar projection state stable across layout changes. Revalidate Electron authority before opening canonical folders, reject mapped Windows network drives in both desktop clients, and add focused server, UI, native, and CI regression coverage for the reviewed races.
Gatekeeper cycle 6Review target:
|
Resolve native session locations from workspace identity before legacy directory fallbacks, carry private mutation context across queued proxy requests, and quiesce active upstream mutations before workspace shutdown. Share map CAS state through the canonical Git common directory, refresh repository identity at lifecycle boundaries, fence concurrent deletion reconciliation, and retain searched sidebar projections while unmounted. Isolate Electron previews on a dedicated protocol, pin folder opening to the live CLI generation and renderer authority, cap lookup responses, retain directory identity through dispatch, and cover the reviewed races in server, UI, Electron, Tauri, and CI tests.
Gatekeeper cycle 7Review target:
|
Protect repository session, map, and destructive operations with an abortable cross-process lock rooted in the canonical Git common directory, while retaining lexical authority during repository identity transitions. Move native sessions through the OpenCode control plane, normalize WSL locations, reject abandoned or oversized mutations before forwarding, and keep runtime blockers until descendant cleanup is confirmed. Scope preview protocols per token, preserve renderer and directory identity through native dispatch, fence UI locations and leases by lifecycle generation, and extend Windows plus cross-process CI coverage.
Gatekeeper cycle 8Review target:
|
Make repository lock recovery and release ownership-safe with immutable process identity, private fallback roots, and atomically detached stale records, then track workspace lifetime leases across CodeNomad processes. Separate host and WSL-native paths through admission, map migration, and control-plane moves; serialize clone and Git file mutations; preserve admission handoff and streamed HTTP error responses. Scope UI deletion, search, interruption, and family operations to lifecycle generations, replace eager retries with backoff, simplify previews to sandboxed HTTP frames, and retain bigint directory identity plus native response deadlines.
Gatekeeper cycle 9Review target:
|
Keep workspace mutations, leases, and native directory opens authoritative across concurrent processes and Windows/WSL hosts. Control replies can now complete synchronous prompts without weakening admission for unrelated mutations, while confirmed deletions reconcile asynchronously. Use deterministic private state roots, host-aware heartbeat fences, canonical missing-path locks, strict shared-map migration, monotone session location updates, and document-bound native capabilities. Bound Electron and Tauri directory lookups and close incomplete proxy uploads safely. Cover lock cleanup and foreign-host expiry, clone admission, map conflicts, rapid session moves, background deletion reconciliation, native capability checks, and request deadlines. Validated server, UI, Electron, and Tauri suites plus typechecks and the UI build.
Gatekeeper cycle 10
|
Keep one adjacent repository lock across clone topology changes and publish workspace leases in repository-visible locations shared by Windows and WSL. Foreign-host claims now fail closed without an unsafe timeout, while release remains retryable after transient filesystem failures. Fence locally committed session moves until server confirmation, advance location authority on unchanged list results, and restore same-origin preview element comments with the original cross-origin availability guard. Add regression coverage for pre-clone locking, cross-host path convergence, foreign claims, release retries, shared lifetime leases, delayed session updates, unchanged list timestamps, and preview comment context. Validated 399 server tests, 255 UI tests, 133 Electron tests, 97 Tauri tests, typechecks, and the UI production build.
Gatekeeper cycle 11
|
Anchor pre-Git locks beneath the destination parent even when intermediate directories appear, converge Windows and WSL drive identities, and accept verified DrvFS permission projection without relaxing normal POSIX lock roots. Discover pre-Git leases through linked worktrees, fail closed for nonstandard common directories, retain upstream mutation cleanup until release succeeds, and retry claim retirement without treating a missing heartbeat as a completed release. Normalize superseded Windows session locations so equivalent slash, case, and trailing-separator spellings cannot bypass pending move fences. Covered each edge case with focused tests and validated 406 server tests, 61 browser UI tests, and root typechecks.
Gatekeeper cycle 12
|
Keep repository and lease cleanup pending until every ownership claim retires, canonicalize existing directory aliases, resolve custom WSL drive mounts, and distinguish WSL PID namespaces so live foreign owners cannot be retired locally. Restrict Electron and Tauri renderer authority to shell documents and block top-level same-origin preview navigation before it can replace the shell. Preserve iframe preview networking while preventing history-based native capability bypasses. Clear stale workspace IDs on directory-only moves, compare current Windows locations canonically, and normalize bounded preview metadata before building comment Markdown. Added focused coverage and validated 410 server tests, 69 UI tests, 133 Electron tests, 98 Tauri tests, and typechecks.
Gatekeeper cycle 13
|
Let legacy permission replies use the instance control lane without reacquiring a repository lock held by their prompting mutation, and resolve YOLO persistence location only after queued move admission completes. Preserve shared launches when one creator disconnects, memoize concurrent lifetime-lease release, retain Windows mount source prefixes, and safely converge default WSL drive identity when Windows-side path conversion is unavailable. Route WSL-native root and feature sessions through authoritative workspace IDs while keeping host and native path equality namespace-aware. Validated 415 server tests, 68 browser UI tests, and root typechecks.
Gatekeeper cycle 14
|
Allow permission, question, and abort controls to join an active prompt lane even when an exclusive repository mutation is queued. Disconnecting workspace creators now release only their own ownership while shared launches remain available to other callers. Expose server-resolved OpenCode-native worktree directories and use them for workspace discovery and reactive session routing across default DrvFS, custom automounts, and WSL UNC repositories. Cover control admission ordering, authoritative abort routing, request cancellation, native workspace-list scoping, and reactive UI mapping. Validated with 423 passing server tests, 72 focused browser tests, and root typecheck.
Gatekeeper cycle 15Review target:
|
Summary
Implementation
Git remains authoritative for the worktree inventory while OpenCode remains authoritative for session locations. Legacy metadata.codenomad.worktreeSlug values and the local session map are read only for migration, then removed transactionally after a successful native move.
Session families move as a unit. The UI rejects incomplete family resolution, serializes concurrent operations per family, refreshes authoritative OpenCode state after warps, and restores prior locations and metadata if preparation or deletion fails.
The file-manager bridge is exposed only to local Electron and Tauri windows. Native handlers reject remote callers and UNC paths, canonicalize the requested directory, and require an exact match in git worktree list --porcelain -z before invoking the platform shell.
Windows CI exposed the existing cross-host legacy-marker publication race fixed by commit 8379098 from #618. This branch includes that targeted bounded-rescan fix and its deterministic regression test so Electron and Tauri cannot both remain secondary during simultaneous startup. The Windows crate runs serially because these tests launch real Node and PowerShell processes; each test still exercises its intended internal concurrency.
Validation
Closes #319